/**
 * Copyright 2009 the original author or authors.
 *
 * Licensed under the GPL License, Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.gnu.org/licenses/lgpl-3.0.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.guardianapis.api.http;

import java.io.ByteArrayInputStream;
import java.io.IOException;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodRetryHandler;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NoHttpResponseException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.guardianapis.api.client.GuardianConstants;
import com.guardianapis.api.exceptions.ConfigurationException;

public class CommonsHttpClient implements GuardianHttpClient {

	/*******************************************************************************
	 * Constants
	 *******************************************************************************/

	private static Log log = LogFactory.getLog(CommonsHttpClient.class);

	/*******************************************************************************
	 * Properties
	 *******************************************************************************/
	
	private HttpClient client;
	private int connectionTimeout;
	private int executionTimeout;
	private int maxConnectionsPerHost;
	private int maxTotalConnections;
	private HttpMethodRetryHandler conservativeRetryHandler;

	/*******************************************************************************
	 * Constructors
	 *******************************************************************************/

	public CommonsHttpClient() {
		MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
		this.client = new HttpClient(connectionManager);
		
		this.connectionTimeout = GuardianConstants.DEFAULT_CONNECTION_TIMEOUT;
		this.executionTimeout = GuardianConstants.DEFAULT_EXECUTION_TIMEOUT;
		this.maxConnectionsPerHost = GuardianConstants.DEFAULT_MAX_CONNECTIONS_PER_HOST;
		this.maxTotalConnections = GuardianConstants.DEFAULT_TOTAL_MAX_CONNECTIONS;
				
		this.conservativeRetryHandler = new HttpMethodRetryHandler() {
			public boolean retryMethod(final HttpMethod method, final IOException exception, int executionCount) {
				if (executionCount >= 1) {
					return false; // Do not retry if over max retry count
				}
				if (exception instanceof NoHttpResponseException) {
					return false; // Retry if the server dropped connection on us
				}
				if (!method.isRequestSent()) {
					return true; // Retry if the request has not been sent fully or if it's OK to retry methods that have been sent
				}
				return false; // otherwise do not retry
			}
		};		
	}
	
	public void initialise() throws ConfigurationException {
		try {
			log.info("Initializing the HTTP client browser");
			this.client.getParams().setSoTimeout(executionTimeout);
			this.client.getHttpConnectionManager().getParams().setConnectionTimeout(connectionTimeout);
			log.info("Setting the connection timeout to: " + connectionTimeout + " and the execution timeout to: " + executionTimeout + " for the Browser");
			this.client.getHttpConnectionManager().getParams().setDefaultMaxConnectionsPerHost(maxConnectionsPerHost);
			this.client.getHttpConnectionManager().getParams().setMaxTotalConnections(maxTotalConnections);
			log.info("Setting the maximum connections per host to : " + maxConnectionsPerHost + " and the maximum total number of connections to: " + maxTotalConnections + " for the Browser");
		} catch (Exception e) {
			throw new ConfigurationException("Error initializing the Browser",e);
		}
	}
	
	/*******************************************************************************
	 * Implementing GuardianHttpClient
	 *******************************************************************************/

	public GuardianHttpResponse requestUrl(String pUrl) {
		String currentUrl = pUrl;
		Header locationHeader;
		GetMethod httpRequest = null;
		ByteArrayInputStream responseStream;
		GuardianHttpResponse response;
		
		int statusCode = -1;
		
		try {
			try {
				while (statusCode == -1 || statusCode == GuardianHttpClient.HTTP_STATUS_CODE_REDIRECT) {
					log.debug("Making browser request to: " + currentUrl);
					httpRequest = new GetMethod(currentUrl);
					httpRequest.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, conservativeRetryHandler);
					httpRequest.setFollowRedirects(false);
					
					statusCode = client.executeMethod(httpRequest);
					log.debug("Response code: " + statusCode);

					if (statusCode == GuardianHttpClient.HTTP_STATUS_CODE_REDIRECT) {
						// This is a redirect, so find the url to redirect to and then re-request
						locationHeader = httpRequest.getResponseHeader("location");
						currentUrl = locationHeader.getValue();
						log.debug("Initial response was a redirect to: " + currentUrl + " - will iterate through another request");
					} 
				}
				
				// If the request was successful we create a response using the input stream returned by the HTTP GET request
				
				if (statusCode == GuardianHttpClient.HTTP_STATUS_CODE_SUCCESS) {
					byte[] httpResponseBytes = httpRequest.getResponseBody();
					
					if (log.isTraceEnabled()) {
						log.debug("Raw response: " + new String(httpResponseBytes));
					}
					responseStream = new ByteArrayInputStream(httpResponseBytes);
					response = new GuardianHttpResponse(responseStream);
				} else {
					response = new GuardianHttpResponse("Returned HTTP status code of: " + statusCode + " for url: " + pUrl);
				}
				
			} catch (Exception e) {
				
				// Creating a response from the error generated by trying to make the HTTP GET request
				
				response = new GuardianHttpResponse("Error retrieving content from url: " + currentUrl,e);
			} 
		} finally {
			if (httpRequest != null) {
				// This call is crucial to ensure that the multi-threaded HTTPConnection manager underlying HTTPClient does not run out of resources
				httpRequest.releaseConnection();
			}
		}
		
		return response;
	}

	/*******************************************************************************
	 * Getters / Setters 
	 *******************************************************************************/

	public int getConnectionTimeout() {
		return this.connectionTimeout;
	}

	public void setConnectionTimeout(int pConnectionTimeout) {
		this.connectionTimeout = pConnectionTimeout;
	}

	public int getExecutionTimeout() {
		return this.executionTimeout;
	}

	public void setExecutionTimeout(int pExecutionTimeout) {
		this.executionTimeout = pExecutionTimeout;
	}

	public int getMaxConnectionsPerHost() {
		return this.maxConnectionsPerHost;
	}

	public void setMaxConnectionsPerHost(int pMaxConnectionsPerHost) {
		this.maxConnectionsPerHost = pMaxConnectionsPerHost;
	}

	public int getMaxTotalConnections() {
		return this.maxTotalConnections;
	}

	public void setMaxTotalConnections(int pMaxTotalConnections) {
		this.maxTotalConnections = pMaxTotalConnections;
	}

}
